React State 本身不會負責在重新整理或關閉網頁後保留資料。當網頁重新開啟,Component 會再次建立,State 也會重新取得初始值。
不過目前的專案重新開啟後,原本加入的作品仍然存在。繼續往下看,可以發現專案使用了瀏覽器提供的 localStorage 保存資料。
相關程式碼放在:
src/utils/storage.ts
裡面有兩個函式:
loadItems()
saveItems()
從名稱大概可以猜到,loadItems() 負責讀取資料,saveItems() 則負責保存資料。
localStoragelocalStorage 是瀏覽器提供的儲存空間。重新整理或關閉網頁後,裡面的內容通常仍然會保留,下次開啟同一個網頁時,就能再次讀取。
但因為它不是後端資料庫,所以資料不會跟著帳號同步到其他裝置。清除瀏覽器資料後,保存的內容也可能跟著消失。
MDN:使用 Web Storage API
storage.ts 裡負責保存資料的函式是:
const STORAGE_KEY = 'anime-manga-collection'
export function saveItems(items: CollectionEntry[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items))
}
localStorage.setItem() 需要提供資料的名稱與內容。這裡的 STORAGE_KEY 就像替收藏資料貼上的標籤,之後讀取時也要使用相同的名稱。
另外,localStorage 只能保存字串,但 items 是一個包含多筆物件的陣列,因此需要先使用:
JSON.stringify(items)
將陣列轉換成 JSON 字串,再存進瀏覽器。
MDN:Storage.setItem()
saveItems() 什麼時候執行?App.tsx 使用 useEffect() 呼叫 saveItems():
useEffect(() => {
saveItems(items)
}, [items])
這段 Effect 會在第一次 Render 完成後執行。之後新增、修改或刪除作品,讓 items 發生變化時,也會再次執行,把最新的收藏資料存進 localStorage。
這也是 Day 13 認識 useEffect() 後,在實際專案中看到的使用方式:當 State 改變後,讓瀏覽器裡保存的資料也跟著更新。
負責讀取資料的是 loadItems():
export function loadItems(): CollectionEntry[] {
try {
const savedItems = localStorage.getItem(STORAGE_KEY)
return savedItems ? JSON.parse(savedItems) : []
} catch {
return []
}
}
localStorage.getItem() 會根據 STORAGE_KEY 尋找之前保存的內容。
如果有找到,就使用:
JSON.parse(savedItems)
把 JSON 字串還原成 JavaScript 可以使用的陣列;如果沒有找到資料,就回傳空陣列。
可以先這樣記:
JSON.stringify():陣列或物件 → 字串
JSON.parse():字串 → 陣列或物件
外面的 try...catch 則是用來處理讀取失敗的情況。例如保存的內容不是正確的 JSON 格式,JSON.parse() 就可能發生錯誤。這時候程式會改成回傳空陣列,讓頁面還是可以繼續顯示。
回到 App.tsx,items 是這樣建立的:
const [items, setItems] = useState<CollectionEntry[]>(loadItems)
這裡傳入的是 loadItems 這個函式本身。React 會在建立 State 時呼叫它,再將回傳的結果當成 items 的初始值。
因此網頁開啟時會先看看瀏覽器裡有沒有以前保存的收藏資料。有資料就從原本的清單開始,沒有資料或讀取失敗就從空陣列開始。
React 官方文件:將初始化函式傳給 useState
items 發生變化
↓
useEffect() 執行 saveItems()
↓
JSON.stringify() 將資料轉成字串
↓
localStorage 保存資料
重新開啟網頁
↓
loadItems() 讀取資料
↓
JSON.parse() 還原收藏陣列
↓
成為 items 的初始值
localStorage 可以將資料保存在目前使用的瀏覽器中JSON.stringify() 負責將陣列轉成字串,JSON.parse() 負責將字串還原items 改變後,useEffect() 會呼叫 saveItems()
items 時,loadItems() 會提供初始資料下篇見~